//------------------------------------------------------------
//  Filename: mbox.sv
//   
//  Author  : wlduan@gmail.com
//  Revise  : 2020-12-03 10:16
//  Description: 
//   
//  Copyright (C) 2014, UCCHIP, Inc. 					      
//  All Rights Reserved.                                       
//-------------------------------------------------------------
//
`timescale 1ns/1ps
 
interface FIFO_IF #(
    parameter DATA_WIDTH = 32
);
    logic[DATA_WIDTH -1 : 0] data ;
    logic                    ready;
    logic                    valid;

    modport PUT(
        input  data,valid,
        output ready
    );

    modport GET(
        output data,valid,
        input  ready
    );    

endinterface

interface BUF_IF #( 
    parameter ADDR_WIDTH = 8, 
    parameter DATA_WIDTH = 32
);
    logic [ADDR_WIDTH-1:0] addr; 
    logic                  req; 
    logic                  gnt; 
    logic [DATA_WIDTH-1:0] wdata; 
    logic                  we; 
    logic [DATA_WIDTH-1:0] rdata; 
    logic                  rvalid; 
    
    modport Master ( 
        output req,addr,we,wdata, 
        input  rdata,rvalid,gnt 
    ); 

    modport Slave ( 
        input  req,addr,we,wdata, 
        output rdata,rvalid,gnt 
    ); 

endinterface 
//-------------------------------------------------------------
//---
//---
//-------------------------------------------------------------
module mbox #(
    parameter N_PORTS     = 8,
    parameter INFO_WIDTH  = 8,
    parameter DATA_WIDTH  = 24,
    parameter PORT_DEEPTH = 16,
    parameter PORT_PTR_BITS = $clog2(PORT_DEEPTH),
    parameter ADDR_WIDTH    = $clog2(PORT_DEEPTH*N_PORTS),
    parameter N_CHANNEL   = 2*N_PORTS
)
( 
    input  logic       clk,  
    input  logic       rstn,  
     
    input  logic[31:0] mbox_clear,
    output logic[31:0] mbox_state,

    FIFO_IF.PUT        put_if[N_PORTS],
    FIFO_IF.GET        get_if[N_PORTS]
);    
//-------------------------------------------------------
logic[PORT_PTR_BITS-1 : 0] wptr[N_PORTS-1:0];
logic[PORT_PTR_BITS-1 : 0] rptr[N_PORTS-1:0];
logic[PORT_PTR_BITS   : 0] bcnt[N_PORTS-1:0]; 
logic[N_PORTS-1       : 0] wvalid,wready,rvalid,rready;
//-------------------------------------------------------
logic[2*N_PORTS-1:0] mem_req;
//-------------------------------------------------------
BUF_IF lint_master(); 
BUF_IF lint_slave[2*N_PORTS](); 
//-------------------------------------------------------
genvar i;
generate 
    for(i = 0;i < N_PORTS;i++) begin
        assign lint_slave[i].req   = put_if[i].valid;
        assign lint_slave[i].addr  = wptr[i] + i*PORT_DEEPTH; 
        assign lint_slave[i].wdata = put_if[i].data; 
        assign lint_slave[i].we    = 1'b1;

        assign lint_slave[N_PORTS + i].req   = get_if[i].ready&rvalid[i];
        assign lint_slave[N_PORTS + i].addr  = rptr[i] + i*PORT_DEEPTH; 
        assign lint_slave[N_PORTS + i].wdata = 'b0; 
        assign lint_slave[N_PORTS + i].we    = 1'b0; 

        assign get_if[i].valid    = lint_slave[N_PORTS + i].rvalid; 
        assign get_if[i].data     = lint_slave[N_PORTS + i].rdata; 

        assign put_if[i].ready    = wready[i]; 
        //----------------------------------------------------------
        always_ff@(posedge clk,negedge rstn)begin 
            if(!rstn)begin 
                wptr[i] <= 'h0; 
            end
            else if(wvalid[i]&wready[i]) begin 
                wptr[i] <= wptr[i] + 1; 
            end 
        end 
        //----------------------------------------------------------
        always_ff@(posedge clk,negedge rstn)begin 
            if(!rstn)begin 
                rptr[i] <= 'h0; 
            end
            else if(rvalid[i]&rready[i]) begin 
                rptr[i] <= rptr[i] + 1; 
            end 
        end 
        //----------------------------------------------------------
        assign wready[i] = lint_slave[i].gnt&(bcnt[i] < PORT_DEEPTH);
        assign wvalid[i] = put_if[i].valid;
        //----------------------------------------------------------
        assign rvalid[i] = (bcnt[i] > 0);
        assign rready[i] = get_if[i].ready&lint_slave[N_PORTS + i].gnt;
        //----------------------------------------------------------
        always_ff@(posedge clk,negedge rstn)begin 
            if(!rstn) begin
                bcnt[i] <= 'h0; 
            end
            else if(wvalid[i]&wready[i]&rvalid[i]&rready[i])begin 
                bcnt[i] <= bcnt[i]; 
            end 
            else if(rvalid[i]&rready[i]) begin 
                bcnt[i] <= bcnt[i] - 1'b1;
            end 
            else if(wvalid[i]&wready[i]) begin 
                bcnt[i] <= bcnt[i] + 1'b1; 
            end 
        end 
    end
endgenerate
//-------------------------------------------------------
logic [N_CHANNEL-1:0]   active_i; 
logic [N_CHANNEL-1:0]   active_q; 
logic [N_CHANNEL-1:0]   active_req_i; 
logic [N_CHANNEL-1:0]   active_rrvs; 
logic [N_CHANNEL-1:0]   active_prev; 
logic [N_CHANNEL-1:0]   active_mask; 
logic [N_CHANNEL-1:0]   active_expc; 
logic [N_CHANNEL-1:0]   active_int;
logic [N_CHANNEL-1:0]   active_we_i;
logic [2*N_CHANNEL-1:0] active_dreq; 
logic [2*N_CHANNEL-1:0] active_dxpc; 
//-------------------------------------------------------
assign active_rrvs  = ~active_prev - active_prev; 
assign active_mask  = active_rrvs + 1'b1; 
assign active_dreq  = {active_req_i,{active_mask&active_req_i}}; 
assign active_expc  = active_dxpc[0 +: N_CHANNEL]|active_dxpc[N_CHANNEL +: N_CHANNEL]; 
//-------------------------------------------------------
always_ff @(posedge clk,negedge rstn) begin 
    if(rstn == 0)begin 
        active_prev <= 'b0; 
    end 
    else begin 
        active_prev <= active_expc; 
    end 
end 
//-------------------------------------------------------
always_comb begin 
    active_dxpc = '0; 
    for(reg[7:0] i=0;i<2*N_CHANNEL;i++) begin 
        if(active_dreq[i]) begin 
            active_dxpc[i] = 1'b1; 
            break; 
        end 
    end 
end 
//-------------------------------------------------------
assign active_i = active_expc; 
//-------------------------------------------------------
always_ff @(posedge clk,negedge rstn) begin 
    if(rstn == 0) begin 
        active_q <= 'b0; 
    end 
    else begin 
        active_q <= active_int; 
    end 
end 
//-------------------------------------------------------
enum logic[7:0] {IDLE,WAIT_GNT,WAIT_VLD} cs,ns; 
//-------------------------------------------------------
always_ff @(posedge clk,negedge rstn) begin 
    if(rstn == 0)begin 
        cs <= IDLE; 
    end 
    else begin 
        cs <= ns; 
    end 
end 
//-------------------------------------------------------
always_comb begin 
    ns = cs; 
    active_int = active_q; 
    lint_master.req = 1'b0; 
    case(cs) 
        IDLE: begin 
            if(|active_req_i) begin 
                active_int = active_i; 
                lint_master.req = 1'b1; 
                ns = (lint_master.gnt) ? WAIT_VLD : WAIT_GNT; 
            end 
        end 
        WAIT_GNT: begin
            lint_master.req = 1'b1; 
            if(lint_master.gnt) ns = WAIT_VLD; 
        end 
        WAIT_VLD: begin 
            if(lint_master.rvalid) begin 
                if(|active_req_i) begin 
                    active_int = active_i; 
                    lint_master.req = 1'b1; 
                    ns = (lint_master.gnt) ? WAIT_VLD : WAIT_GNT; 
                end 
                else begin 
                    ns = IDLE; 
                end 
            end 
        end 
    endcase 
end 
//-------------------------------------------------------
logic [N_CHANNEL*ADDR_WIDTH -1:0] ports_addr; 
logic [N_CHANNEL*DATA_WIDTH -1:0] ports_wdata; 
//-------------------------------------------------------
generate  
    for(i=0;i<N_CHANNEL;i++) begin 
        assign active_req_i[i] = lint_slave[i].req; 
        assign active_we_i[i]  = lint_slave[i].we; 
        always_comb begin 
            lint_slave[i].rdata  = 'b0; 
            lint_slave[i].rvalid = 'b0; 
            if(active_q[i]) begin 
                lint_slave[i].rdata  = lint_master.rdata; 
                lint_slave[i].rvalid = lint_master.rvalid; 
            end 
        end 
        assign lint_slave[i].gnt = active_int[i]&lint_master.gnt; 
        
        assign ports_addr [ADDR_WIDTH*i +: ADDR_WIDTH] = lint_slave[i].addr[ADDR_WIDTH-1:0]; 
        assign ports_wdata[DATA_WIDTH*i +: DATA_WIDTH] = lint_slave[i].wdata[DATA_WIDTH-1:0]; 
    end 
endgenerate
//-------------------------------------------------------
always_comb begin 
    lint_master.addr  = '0; 
    lint_master.wdata = '0;
    for(reg[7:0] i=0;i<N_CHANNEL;i++) begin 
        lint_master.addr  = lint_master.addr  |(ports_addr [ADDR_WIDTH*i +:ADDR_WIDTH]&{ADDR_WIDTH{active_int[i]}}); 
        lint_master.wdata = lint_master.wdata |(ports_wdata[DATA_WIDTH*i +:DATA_WIDTH]&{DATA_WIDTH{active_int[i]}}); 
    end 
end 
//-------------------------------------------------------
logic                  CEN; 
logic                  WEN; 
logic [ADDR_WIDTH-1:0] A; 
logic [DATA_WIDTH-1:0] D; 
logic [DATA_WIDTH-1:0] Q; 
//-------------------------------------------------------
assign A   =  lint_master.addr[ADDR_WIDTH-1:0]; 
assign WEN = ~lint_master.we; 
assign D   =  lint_master.wdata[DATA_WIDTH-1:0]; 
assign CEN = ~lint_master.req; 

assign lint_master.we    = |(active_int&active_we_i); 
assign lint_master.gnt   = lint_master.req; 
assign lint_master.rdata = Q; 
//-------------------------------------------------------
always @(posedge clk,negedge rstn) begin
    if(rstn == 1'b0) begin 
        lint_master.rvalid <= 'b0; 
    end 
    else begin 
        lint_master.rvalid <= lint_master.req; 
    end 
end  
//-------------------------------------------------------
`ifndef ASIC 
    logic[DATA_WIDTH-1:0] mem[PORT_DEEPTH*N_CHANNEL-1:0]; 
    //-------------------------------------------------------
    always_ff @(posedge clk) if ((~CEN)&(~WEN)) mem[A] <= D     ; 
    always_ff @(posedge clk) if ((~CEN)&(WEN))  Q      <= mem[A];  
`else 
    S55NLLG1PH_X32Y2D32 
    mem 
    ( 
        .CLK ( clk ) , 
        .CEN ( CEN   ) , 
        .WEN ( WEN   ) , 
        .A   ( A     ) , 
        .0   ( D     ) , 
        .Q   ( Q     ) 
    ); 
`endif  

endmodule

`define VCS_TEST
`ifdef  VCS_TEST

module fifo_driver(
    input  logic        wclk,  
    input  logic        wready, 
    output logic[31:0]  wdata,
    output logic        wvalid, 
 
    output logic        rdy_drive
);

    reg[7:0] c_val;
    initial begin
        wdata = 0;
        #2000;
        forever begin
            c_val = {$urandom};
            if(wvalid&wready) wdata <= {$urandom} + c_val;
            @(posedge wclk);
        end
    end
    
    reg[7:0] x_time;
    initial begin
        x_time = 0;
        wvalid = 0;
        forever begin
            x_time = {$urandom} % 100;
            #(x_time*100);
            wvalid = {$urandom}&1;
        end
    end
    
    reg[7:0] y_time;
    initial begin
        y_time = 0;
        rdy_drive = 0;
        forever begin
            y_time = {$urandom} % 100;
            #(y_time*100);
            rdy_drive = {$urandom}&1;
        end
    end

endmodule  


module top (); 

    logic        rstn;  
    
    logic        wclk;  
    logic        rclk;  
 
    initial begin
        rstn = 1'b0;
        #1000 rstn = 1'b1;
    end
    
    initial begin
        wclk = 0;
        forever #100 wclk = ~wclk;
    end
 
    initial begin
        rclk = 0;
        forever #150 rclk = ~rclk;
    end

    FIFO_IF put_if[8]();
    FIFO_IF get_if[8]();

    mbox 
    DUT ( 
        .clk        ( wclk   ),  
        .rstn       ( rstn   ),  
         
        .mbox_clear ( 'b0    ),
    
        .put_if     ( put_if ),
        .get_if     ( get_if )
    );    
 

    generate
        genvar i;
        for(i=0;i<8;i++) begin
            fifo_driver driver
            (
                .wclk       ( wclk               ) ,  
                .wready     ( put_if[i].ready    ) , 
                .wdata      ( put_if[i].data     ) ,
                .wvalid     ( put_if[i].valid    ) , 
 
                .rdy_drive  ( get_if[i].ready    )
            );
        end
    endgenerate

    initial
    begin
      #550000;
      $display("**  Simulation stop ");
      $finish();
    end

    initial begin
      $fsdbDumpfile("wave.fsdb");
      $fsdbDumpvars();
      $fsdbDumpMDA();
    end

endmodule

`endif
